<?php

namespace Celebrio\FileSystem\Drivers;

use Nette\Object;

use Celebrio\FileSystem\Drivers\IDriver;
use Celebrio\FileSystem\DataFile;
use Celebrio\FileSystem\File;

use \Microsoft_WindowsAzure_Storage_Blob;
use \Microsoft_Http_Client_Adapter_Exception;
use \Microsoft_WindowsAzure_Exception;

/**
 * Azure driver
 * This is general azure driver for those who doesn't want to use our File 
 * System but want use Azure Storage for thier own needs.
 * Two opnions are posible. The simplier one is to store data to only one 
 * container. Just set up the container name (by constructor or method 
 * setContainer) and call load/save/move... without other container 
 * specifications on files.
 * You can also do those operations between multiple containers. For this reason,
 * each method has container specification to determinate in which one you 
 * want to store/move/copy etc. 
 *
 * @author honza
 */

class AzureStorage extends AzureStorageDriver implements IDriver {
   
    
    public function getModuleName() {
        return "azure";
    }
    
    
    /* ------------------------------------------------------- */
    
    public function __construct($host = '', $accountName = '', $password = '', $basicContainer = '') {
        $this->setContainer($basicContainer);
        $this->setConnectionSettings($host, $accountName, $basicContainer);
    }


    public function load(DataFile $dataFile, $container = '') {
        
        if($dataFile->isDirectory()) {
            throw new \InvalidArgumentException("Azure storage does not return directory but empty file.", 0);
        }
        $storage = $this->getConnectionSettings();
        $path = $this->getPathForFile($dataFile);
        
        if(empty($container))
            $container = $this->getContainer();
       
        try {
            $data = $storage->getBlobData($container, $path);
        } catch(Microsoft_Http_Client_Adapter_Exception $e) {
            throw new \Albireo\FileSystem\IOException("Connection failed. Check internet connection.", 0, $e);
        } catch(\Microsoft_WindowsAzure_Exception $e) {
            throw new \FileNotFoundException("Some problem occur", 0, $e);
        }
        
        $dataFile->setData($data);
        
        return $dataFile;
    }


    public function save(DataFile $dataFile, $container = '') {        
        if(\strlen($dataFile->getData()) >= self::MAX_BLOB_SIZE) {
                $this->saveLarge($dataFile, $container);
                return;
        }
        $storage = $this->getConnectionSettings();
        $path = $this->getPathForFile($dataFile);
        if(empty($container))
            $container = $this->getContainer();
        
        try {
            $storage->createContainerIfNotExists($container);
            $storage->createResourceName($container, $path);
            
            $storage->putBlobData($container, $path, $dataFile->getData());
            
        } catch(Microsoft_Http_Client_Adapter_Exception $e) {
            throw new \Albireo\FileSystem\IOException("Connection failed. Check internet connection.", "0", $e);
        } catch(\Microsoft_WindowsAzure_Exception $e) {
            throw new \FileNotFoundException("Some problem occur", "0", $e);
        }
    }
    
    /**
     * Writes binary large objects to Azure storage. This method is called when 
     * the size of uploaded file is greater then 64Mb. It takes incoming string
     * (data) and splits them into blocks of data which will be uploaded
     * on server separately and then confirmed by putBlockList method.
     * 
     * @param DataFile $dataFile
     */
    public function saveLargeFile(DataFile $dataFile, $container = '') {    
        if(\strlen($dataFile->getData()) < self::MAX_BLOB_SIZE) {
            $this->save($dataFile);
            return;
        }
        $storage = $this->getConnectionSettings();
        $path = $this->getPathForFile($dataFile);
        if(empty($container))
            $container = $this->getContainer();
        $data = $dataFile->getData();
        try {
            $storage->createContainerIfNotExists($container);
            $numOfBlocks = ceil (\strlen($dataFile->getData()) / self::MAX_BLOCK_SIZE);
            $indexes = $this->getIndexes($numOfBlocks);
            
            //storing blocks on server
            for($i = 0; $i < $numOfBlocks; $i++) {
                $blockData = \substr($data, $i * self::MAX_BLOCK_SIZE, self::MAX_BLOCK_SIZE);
                $storage->putBlock($container, $path, $indexes[$i], $blockData);
                $blockData = null;
                unset($blockData);
            }
            //commit blocks
            $storage->putBlockList($container, $path, $indexes);
        } catch(Microsoft_Http_Client_Adapter_Exception $e) {
            throw new \Albireo\FileSystem\IOException("Connection failed. Check internet connection.", "0", $e);
        } catch(\Microsoft_WindowsAzure_Exception $e) {
            throw new \FileNotFoundException("Some problem occur", "0", $e);
        }
    }
    
    public function delete(DataFile $dataFile, $container = '') {
        $storage = $this->getConnectionSettings();
        $path = $this->getPathForFile($dataFile);
        if(empty($container))
            $container = $this->getContainer();
        
        try {
            $storage->deleteBlob($container, $path);
        } catch(Microsoft_Http_Client_Adapter_Exception $e) {
            throw new \Albireo\FileSystem\IOException("Connection failed. Check internet connection.", "0", $e);
        } catch(\Microsoft_WindowsAzure_Exception $e) {
            throw new \FileNotFoundException("Some problem occur", "0", $e);
        }
        
    }
    
    public function move(DataFile $source, DataFile $target, $container = '') {
        $storage = $this->getConnectionSettings();
        $sourcePath = $this->getPathForFile($source);
        $targetPath = $this->getPathForFile($target);
        
        if($sourcePath === $targetPath) {
            throw new InvalidFilesystemOperation("Move the file on the same file is not allowed");
        }
        
        if(empty($container))
            $container = $this->getContainer();
        
        try {
            $storage->copyBlob($container, $sourcePath, $container, $targetPath);
            $storage->deleteBlob($container, $sourcePath);
        } catch(Microsoft_Http_Client_Adapter_Exception $e) {
            throw new \Albireo\FileSystem\IOException("Connection failed. Check internet connection.", "0", $e);
        } catch(\Microsoft_WindowsAzure_Exception $e) {
            throw new \FileNotFoundException("Some problem occur", "0", $e);
        }
    }
    
    public function moveBetweenContainers(DataFile $source, $sourceContainer, DataFile $target, $targetContainer) {
        if($sourceContainer === $targetContainer) {
            $this->move($source, $target, $sourceContainer);
            return;
        }
        
        $storage = $this->getConnectionSettings();
        $sourcePath = $this->getPathForFile($source);
        $targetPath = $this->getPathForFile($target);
    
        try {
            $storage->createContainerIfNotExists($targetContainer);
            $storage->copyBlob($sourceContainer, $sourcePath, $targetContainer, $targetPath);
            $storage->deleteBlob($sourceContainer, $sourcePath);
        } catch(Microsoft_Http_Client_Adapter_Exception $e) {
            throw new \Albireo\FileSystem\IOException("Connection failed. Check internet connection.", "0", $e);
        } catch(\Microsoft_WindowsAzure_Exception $e) {
            throw new \FileNotFoundException("Some problem occur", "0", $e);
        }
    }
    
    public function copy(DataFile $source, DataFile $target, $container = '') {
        $storage = $this->getConnectionSettings();
        $sourcePath = $this->getPathForFile($source);
        $targetPath = $this->getPathForFile($target);
        if(empty($container))
            $container = $this->getContainer();
        
        try {
            $storage->copyBlob($container, $sourcePath, $container, $targetPath);
        } catch(Microsoft_Http_Client_Adapter_Exception $e) {
            throw new \Albireo\FileSystem\IOException("Connection failed. Check internet connection.", "0", $e);
        } catch(\Microsoft_WindowsAzure_Exception $e) {
            throw new \FileNotFoundException("Some problem occur", "0", $e);
        }
    }
    
    public function copyBetweenTwoContainers(DataFile $source, $sourceContainer, DataFile $target, $targetContainer) {
        if($sourceContainer === $targetContainer) {
            $this->copy($source, $target, $sourceContainer);
            return;
        }
        
        $storage = $this->getConnectionSettings();
        $sourcePath = $this->getPathForFile($source);
        $targetPath = $this->getPathForFile($target);
            
        try {
            $storage->createContainerIfNotExists($targetContainer);
            $storage->copyBlob($container, $sourcePath, $container, $targetPath);
        } catch(Microsoft_Http_Client_Adapter_Exception $e) {
            throw new \Albireo\FileSystem\IOException("Connection failed. Check internet connection.", "0", $e);
        } catch(\Microsoft_WindowsAzure_Exception $e) {
            throw new \FileNotFoundException("Some problem occur", "0", $e);
        }
    }
    
    public function exists(DataFile $dataFile, $container ='') {
        $storage = $this->getConnectionSettings();
        $path = $this->getPathForFile($dataFile);
        
        if(empty($container))
            $this->getContainer();
        
        try {
            $exists = $storage->blobExists($container, $path);
        } catch(Microsoft_Http_Client_Adapter_Exception $e) {
            throw new \Albireo\FileSystem\IOException("Connection failed. Check internet connection.", "0", $e);
        } catch(\Microsoft_WindowsAzure_Exception $e) {
            throw new \FileNotFoundException("Some problem occur", "0", $e);
        }
        
        return $exists;
    
    }
    
    public function onChangeOwner(DataFile $dataFile) {
        //Do nothing in this moment
    }
    
    /*
     * This method returns the connection informations i.e. new Blob()
     */
    public function getConnectionSettings() {
        if(empty($this->connection)) {
           $this->connection = new Microsoft_WindowsAzure_Storage_Blob(self::$host, self::$accountName, self::$password);
        }
        return $this->connection;
    }
    
    public function setConnection($host, $acc, $password) {
        self::$host = $host;
        self::$accountName = $acc;
        self::$password = $password;
    }
    
    public function getBlobsInContainer($containerName, $prefix = '') {
        return parent::getBlobsInContainer($containerName, $prefix);
    }

    public function getContainers($prefix = null) {
        $storage = $this->getConnectionSettings();
        return $storage->listContainers($prefix);
    }

    public function setContainer($containerName) {
        parent::setContainer($containerName);
    }
    
    public static function setConnectionSettings($accountName, $password) {
        parent::setConnectionSettings($accountName, $password);
    }



}
